/**
 * Copyright (C) 2009 Noel Ang
 *
 * This file is part of Samsara.
 *
 * Samsara is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Samsara is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Samsara.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package com.lunarshades.samsara.ui.actions;

import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.Reader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.ImageIcon;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.Keymap;
import javax.swing.text.PlainDocument;

import com.lunarshades.samsara.Raider;
import com.lunarshades.samsara.ui.ListingDialog;
import com.lunarshades.samsara.ui.RosterTableModel;
import com.lunarshades.samsara.ui.SamsaraFrame;
import com.lunarshades.samsara.ui.Utils;
import com.lunarshades.samsara.ui.fonts.Fonts;

/**
 */
public class KarmaContestAction extends KarmaChangeAction
        implements ListSelectionListener {

public KarmaContestAction(RosterTableModel<? extends Raider> model,
                          JTable table,
                          SamsaraFrame app
) {
    super(NAME);
    mTable = table;
    mModel = model;
    mApp = app;
    mWinnerBoxActionListener = new WinnerBoxActionListener();

//    URL largeIconUrl = getClass().getResource(
//            "/com/lunarshades/samsara/ui/icons/choose.png"
//    );
    URL smallIconUrl = getClass().getResource(
            "/com/lunarshades/samsara/ui/icons/choose_small.png"
    );
    putValue(Action.SMALL_ICON, new ImageIcon(smallIconUrl));
//    putValue(Action.LARGE_ICON_KEY, new ImageIcon(largeIconUrl));
    putValue(Action.SHORT_DESCRIPTION, SHORT_TEXT);
    putValue(Action.MNEMONIC_KEY, KeyEvent.VK_W);
    setEnabled(!mTable.getSelectionModel().isSelectionEmpty());
    // Listen to selection changes to enable/disable the action as appropriate
    mTable.getSelectionModel().addListSelectionListener(this);
}

private Collection<Raider> findWinners(Set<Raider> contestants) {
    Collection<Raider> winners = new TreeSet<Raider>();
    if (!contestants.isEmpty()) {
        for (Raider raider : contestants) {
            int compare = 0;
            for (Raider winner : winners) {
                compare = raider.karma().compareTo(winner.karma());
                // raider ties a winner, or raider has more karma
                if (compare >= 0) {
                    break;
                }
            }
            if (compare < 0) {
                // raider did not beat any of the winners
                // change nothing
            } else {
                if (compare > 0) {
                    // current raider beat (all of) the
                    // current winner(s), throw winners out
                    winners.clear();
                }
                // compare == 0 || compare > 0
                // raider replaces winners or ties with them
                winners.add(raider);
            }
        }
    }
    return winners;
}

private void initializeWinnerDisplay(Collection<Raider> winners,
                                     boolean isContested) {
    assert winners != null;

    if (winners.size() > 0) {
        boolean winnersTied = winners.size() > 1;

        // Set winner box with winner(s).
        mWinnerBox.removeActionListener(mWinnerBoxActionListener);
        mWinnerBox.removeAllItems();
        for (Raider raider : winners) {
            mWinnerBox.addItem(raider);
        }
        mWinnerBox.setFocusable(winners.size() > 1);

        // If there are multiple winners (ties), force operator to
        // select a winner by not pre-selecting any of them, and
        // disabling the "OK" button until there is a selection.
        mDialog.CommitAction.setEnabled(!winnersTied);
        mWinnerBox.setSelectedIndex(!winnersTied ? 0 : -1);
        mWinnerBox.addActionListener(mWinnerBoxActionListener);

        // Compute the static karma cost projections; round up
        // If there are multiple winners, they are all tied so it
        // doesn't matter which datum to use.
        Raider aWinner = winners.iterator().next();
        int karma;
        try {
            karma = Utils.makeInt(aWinner.karma().toString());
        } catch (NumberFormatException e) {
            karma = 0;
        }
        int half = (karma / 2);
        int fourth = (karma / 4);
        mKarmaTextField.setText(aWinner.karma().toString());
        mDeductHalfTextField.setText(String.valueOf(half));
        mDeductFourthTextField.setText(String.valueOf(fourth));


        // Initial focus and radio button selection:
        // 1. The comment text field always gets initial focus
        // 2. The "half" button is the selected button in the group when
        //    there are multiple contestants. Otherwise, the "fourth"
        //    button is selected.
        ButtonModel activeButtonModel = (winners.size() > 1 || isContested
                                         ? mDeductHalfButton.getModel()
                                         : mDeductFourthButton.getModel());
        mDeductButtonGroup.setSelected(activeButtonModel, true);
        mCommentTextField.requestFocusInWindow();
        mCommentTextField.setText(importCommentFromClipboard());

        // Update balance field
        updateBalanceDisplay(
                retrieveSelectedKarmaCost(mDeductButtonGroup.getSelection()));
    }
}

private String importCommentFromClipboard() {
    // Retrieve clipboard contents, if possible.
    String imported = "";
    try {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        // requestor argument not used
        Transferable content = clipboard.getContents(null);
        DataFlavor flavor = DataFlavor.selectBestTextFlavor(
                content.getTransferDataFlavors());
        if (flavor != null) {
            Reader reader = flavor.getReaderForText(content);
            char[] charContent = new char[100];
            int contentLength = reader.read(charContent);
            reader.close();

            // Find a span of characters in the extracted
            // clipboard contents that are enveloped by '[' and ']'.
            String contentString = String.valueOf(charContent, 0, contentLength);
            int startIndex = Math.max(0, contentString.indexOf('['));
            int endIndex = Math.max(0, contentString.indexOf(']', startIndex));
            if (startIndex < endIndex) {
                imported =
                        contentString.substring(startIndex + 1, endIndex)
                                .trim();
            }

        }
    } catch (Exception ignored) {
    }
    return imported;
}

private String retrieveSelectedKarmaCost(ButtonModel pActiveButtonModel) {
    JComponent valueReference = mCostOptionsToValuesMap.get(pActiveButtonModel);
    String value = null;
    if (valueReference != null) {
        if (valueReference instanceof JTextComponent) {
            value = ((JTextComponent) valueReference).getText();
        }
    }
    return value;
}

private void updateBalanceDisplay(String subtractAmount) {
    int karma;
    int cost;

    try {
        karma = Utils.makeInt(mKarmaTextField.getText());
    } catch (NumberFormatException e) {
        karma = 0;
    }
    try {
        cost = Utils.makeInt(subtractAmount);
    } catch (NumberFormatException e) {
        cost = 0;
    }

    mBalanceTextField.setText(String.valueOf(Math.max(0, karma - cost)));
}

private void updateBalanceDisplay(int subtractAmount) {
    int karma;

    try {
        karma = Utils.makeInt(mKarmaTextField.getText());
    } catch (NumberFormatException e) {
        karma = 0;
    }

    mBalanceTextField.setText(String.valueOf(Math.max(0, karma - subtractAmount
    )
    )
    );
}

private int calculateFraction() {
    int karma;
    int fraction;

    try {
        karma = Utils.makeInt(mKarmaTextField.getText());
    } catch (NumberFormatException e) {
        karma = 0;
    }
    
    try {
        fraction = Utils.makeInt(mDeductOtherFractionTextField.getText());
    } catch (NumberFormatException e) {
        fraction = 0;
    }

    int kf = karma * fraction;
    return kf / 100;
}

private void initComponent() {
    if (mDialog == null) {
        initLayout();

    }
    mDialog.clearBatch();
    mDeductHalfButton.requestFocusInWindow();
}

private void initLayout() {

    final Insets BREAK_INSETS = new Insets(10, 5, 1, 5);
    final Insets CONTINUATION_INSETS = new Insets(1, 5, 1, 5);

    final GridBagConstraints c = new GridBagConstraints();
    c.insets = CONTINUATION_INSETS;
    c.anchor = GridBagConstraints.WEST;

    JPanel winnerPanel = new JPanel(new GridBagLayout());

    // Row: Winner
    JLabel winnerLabel = new JLabel("Winner:");
    winnerLabel.setFont(winnerLabel.getFont().deriveFont(Font.BOLD));
    mWinnerBox = new JComboBox();
    mWinnerBox.setEditable(false);
    mWinnerBox.setFocusable(false);
    mWinnerBox.setFont(mWinnerBox.getFont().deriveFont(Font.BOLD));
    c.gridy = 0;
    winnerPanel.add(winnerLabel, c);
    c.weightx = 1;
    c.fill = GridBagConstraints.HORIZONTAL;
    winnerPanel.add(mWinnerBox, c);

    // Row: Current Karma
    JLabel currentKarmaLabel = new JLabel("Karma:");
    currentKarmaLabel.setFont(currentKarmaLabel.getFont().deriveFont(Font.BOLD));
    mKarmaTextField = new JTextField(8);
    mKarmaTextField.setEditable(false);
    mKarmaTextField.setFocusable(false);
    mKarmaTextField.setHorizontalAlignment(JTextField.TRAILING);
    mKarmaTextField.setFont(Fonts.getDerivedNumberFont(mKarmaTextField.getFont()));
    mKarmaTextField.setBackground(new Color(200, 200, 255));
    c.gridy = 1;
    c.weightx = 0;
    c.insets = BREAK_INSETS;
    c.fill = GridBagConstraints.NONE;
    winnerPanel.add(currentKarmaLabel, c);
    winnerPanel.add(mKarmaTextField, c);

    // Row: Karma cost (label)
    JLabel costLabel = new JLabel("Karma Cost:");
    c.insets = BREAK_INSETS;
    c.gridy = 2;
    winnerPanel.add(costLabel, c);

    // Row: Karma cost - Half
    mDeductHalfButton = new JRadioButton("Half", true);
    mDeductHalfTextField = new JTextField(new DocumentImpl(), null, 8);
    mDeductHalfTextField.setFont(Fonts.getDerivedNumberFont(mDeductHalfTextField.getFont()));
    mDeductHalfButton.setMnemonic('h');
    mCostOptionsToValuesMap.put(mDeductHalfButton.getModel(),
            mDeductHalfTextField
    );
    mDeductHalfTextField.setEditable(false);
    mDeductHalfTextField.setFocusable(false);
    mDeductHalfTextField.setHorizontalAlignment(JTextField.TRAILING);
    c.insets = CONTINUATION_INSETS;
    c.gridy = 3;
    winnerPanel.add(mDeductHalfButton, c);
    winnerPanel.add(mDeductHalfTextField, c);

    // Row: Karma cost - Fourth
    mDeductFourthButton = new JRadioButton("Fourth");
    mDeductFourthTextField = new JTextField(new DocumentImpl(), null, 8);
    mDeductFourthTextField.setFont(Fonts.getDerivedNumberFont(
            mDeductFourthTextField.getFont()
    )
    );
    mDeductFourthButton.setMnemonic('f');
    mCostOptionsToValuesMap.put(mDeductFourthButton.getModel(),
            mDeductFourthTextField
    );
    mDeductFourthTextField.setEditable(false);
    mDeductFourthTextField.setFocusable(false);
    mDeductFourthTextField.setHorizontalAlignment(JTextField.TRAILING);
    c.gridy = 4;
    winnerPanel.add(mDeductFourthButton, c);
    winnerPanel.add(mDeductFourthTextField, c);

    // Row: Karma cost - Other
    JRadioButton deductOtherFractionButton = new JRadioButton("Per Cent:");
    mDeductOtherFractionTextField = new JTextField(new DocumentImpl(), null, 8);
    mDeductOtherFractionTextField.setFont(Fonts.getDerivedNumberFont(
            mDeductOtherFractionTextField.getFont()
    )
    );
    deductOtherFractionButton.setMnemonic('c');
    mCostOptionsToValuesMap.put(deductOtherFractionButton.getModel(),
            mDeductOtherFractionTextField
    );
    mDeductOtherFractionTextField.setEditable(false);
    mDeductOtherFractionTextField.setFocusable(false);
    mDeductOtherFractionTextField.setToolTipText(
            "Specify per cent fraction (e.g., 75 for 75%)"
    );
    mDeductOtherFractionTextField.setHorizontalAlignment(JTextField.TRAILING);
    mDeductOtherFractionTextField.getDocument()
            .addDocumentListener(new FractionCostDocumentListener());
    c.gridy = 5;
    winnerPanel.add(deductOtherFractionButton, c);
    winnerPanel.add(mDeductOtherFractionTextField, c);

    // Row: Karma cost - Fixed
    JRadioButton deductFixedAmountButton = new JRadioButton("Points:");
    mDeductFixedAmountTextField = new JTextField(new DocumentImpl(), null, 8);
    mDeductFixedAmountTextField.setFont(Fonts.getDerivedNumberFont(
            mDeductFixedAmountTextField.getFont()
    )
    );
    deductFixedAmountButton.setMnemonic('p');
    mCostOptionsToValuesMap.put(deductFixedAmountButton.getModel(),
            mDeductFixedAmountTextField
    );
    mDeductFixedAmountTextField.setEditable(false);
    mDeductFixedAmountTextField.setFocusable(false);
    mDeductFixedAmountTextField.setToolTipText(
            "Specify absolute amount to deduct FROM total"
    );
    mDeductFixedAmountTextField.setHorizontalAlignment(JTextField.TRAILING);
    mDeductFixedAmountTextField.getDocument()
            .addDocumentListener(new FixedCostDocumentListener());
    c.gridy = 6;
    winnerPanel.add(deductFixedAmountButton, c);
    winnerPanel.add(mDeductFixedAmountTextField, c);

    // Row: Karma balance
    JLabel balanceLabel = new JLabel("Balance:");
    balanceLabel.setFont(balanceLabel.getFont().deriveFont(Font.BOLD));
    c.insets = new Insets(4, 5, 0, 5);
    c.gridy = 7;
    c.insets = BREAK_INSETS;
    winnerPanel.add(balanceLabel, c);
    mBalanceTextField = new JTextField(new DocumentImpl(), null, 8);
    mBalanceTextField.setFont(Fonts.getDerivedNumberFont(mBalanceTextField.getFont()));
    mBalanceTextField.setEditable(false);
    mBalanceTextField.setFocusable(false);
    mBalanceTextField.setHorizontalAlignment(JTextField.TRAILING);
    mBalanceTextField.setBackground(new Color(255, 200, 200));
    winnerPanel.add(mBalanceTextField, c);

    // Row: Comment line
    JLabel commentLabel = new JLabel("Comment:");
    c.insets = new Insets(4, 5, 0, 5);
    c.gridy = 8;
    c.insets = BREAK_INSETS;
    winnerPanel.add(commentLabel, c);
    mCommentTextField = new JTextField(null, 15);
    mCommentTextField.setEditable(true);
    mCommentTextField.setFocusable(true);
    mCommentTextField.setHorizontalAlignment(JTextField.LEADING);
    winnerPanel.add(mCommentTextField, c);

    mDeductButtonGroup = new ButtonGroup();
    mDeductButtonGroup.add(mDeductHalfButton);
    mDeductButtonGroup.add(mDeductFourthButton);
    mDeductButtonGroup.add(deductOtherFractionButton);
    mDeductButtonGroup.add(deductFixedAmountButton);
    mDeductButtonGroup.setSelected(mDeductHalfButton.getModel(), true);
    mDeductHalfButton.addActionListener(new HalfCostActionListener());
    mDeductFourthButton.addActionListener(new FourthCostActionListener());
    deductOtherFractionButton.addActionListener(new FractionCostActionListener());
    deductFixedAmountButton.addActionListener(new FixedCostActionListener());

    mDialog = new ListingDialog<Raider>("Resolve Winner",
            "Participants: ",
            mTable.getTopLevelAncestor(),
            winnerPanel
    );

    Keymap keymap;
    {
        keymap = mDeductOtherFractionTextField.getKeymap();
        keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,
                0
        ), mDialog.CommitAction
        );
        keymap = mDeductFixedAmountTextField.getKeymap();
        keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0
        ), mDialog.CommitAction
        );
    }
}

public void actionPerformed(ActionEvent e) {
    // Get the list of raider [table indices] that are part of this operation.
    // Exclude pick-ups but flag their presence.
    synchronized (mModel) {
        int numPickups = 0;
        int[] selectedRows = mTable.getSelectedRows();
        if (selectedRows.length > 0) {
            // Remove selected pick-ups from the selection set.
            // !! Converting view indices to model indices too !!
            ArrayList<Integer> participants = new ArrayList<Integer>();
            for (int row : selectedRows) {
                int aModelRow = mTable.convertRowIndexToModel(row);
                Raider raider = mModel.getRowAt(aModelRow);
                if (raider.pickup()) {
                    ++numPickups;
                } else {
                    participants.add(aModelRow);
                }
            }
            // Rebuild selection indices
            selectedRows = new int[participants.size()];
            for (int i = 0, len = selectedRows.length; i < len; ++i) {
                selectedRows[i] = participants.get(i);
            }
        }

        initComponent();

        // If pick-ups were part of the selection set, show confirm/warning
        // dialog to instruct roll-off, because pick-ups are not part of the
        // karma-based winner resolution.
        //
        // If the operator chooses the OK option, proceed with karma winner
        // resolution, which excludes pick-ups.
        //
        // If the operator chooses the CANCEL option, abort winner resolution
        // (e.g., a pick-up won)
        int decision = JOptionPane.OK_OPTION;
        if (numPickups > 0) {
            StringBuffer message = new StringBuffer();
            message.append("There are ").append(numPickups);
            message.append(" pick-ups included. Perform roll-off.\n");
            message.append("Select OK if contention goes to karma.\n");
            message.append("Select CANCEL otherwise.");
            decision = JOptionPane.showConfirmDialog(
                    mTable.getTopLevelAncestor(),
                    message.toString(),
                    PICKUP_DIALOG_TITLE,
                    JOptionPane.OK_CANCEL_OPTION,
                    JOptionPane.WARNING_MESSAGE
            );
        }

        // Continuing with karma winner resolution
        if (decision != JOptionPane.OK_OPTION) {
            mApp.postStatusMessage(CANCELED_STATUS);
        } else {
            // Add all participants to the interaction dialog
            // Keep in mind selectedRow contains model--not view--indices now
            Map<Raider, Integer> contestantMap = new HashMap<Raider, Integer>();
            for (Integer selectedRow : selectedRows) {
                Raider value = mModel.getRowAt(selectedRow);
                if (mDialog.addToBatch(value)) {
                    contestantMap.put(value, selectedRow);
                }
            }
            Collection<Raider> winners = findWinners(contestantMap.keySet());
            if (winners.size() > 0) {
                initializeWinnerDisplay(winners, contestantMap.size() > 1);
                mDialog.show();
                if (mDialog.isCanceled()) {
                    mApp.postStatusMessage(CANCELED_STATUS);
                } else {
                    try {
                        int oldKarma = Utils.makeInt(mKarmaTextField.getText());
                        int newKarma = Utils.makeInt(mBalanceTextField.getText());
                        int qty = oldKarma - newKarma;
                        String comment = mCommentTextField.getText();
                        Raider r = (Raider) mWinnerBox.getSelectedItem();
                        assert r != null;
                        r.karma().create(0 - qty);
                        int modelRow = contestantMap.get(r);
                        mModel.fireTableRowsUpdated(modelRow, modelRow);
                        contestantMap.remove(r);
                        mApp.postStatusMessage(SUCCESS_STATUS);
                        fireKarmaContestWon(r, contestantMap.keySet(), qty, comment);
                    } catch (NumberFormatException ex) {
                        ex.printStackTrace(System.err);
                    } catch (RosterTableModel.IdentityCollisionException ex) {
                        assert false;
                    }
                }
            }
        }
    }
}

public void valueChanged(ListSelectionEvent e) {
    if (!e.getValueIsAdjusting()) {
        setEnabled(!mTable.getSelectionModel().isSelectionEmpty());
    }
}

// PlainDocument subclass that permits only numbers to be entered
private class DocumentImpl extends PlainDocument {
    private DocumentImpl() {
    }

    @Override
    public void insertString(int offs, String str, AttributeSet a)
            throws BadLocationException
    {
        if (Utils.isNumberText(str)) {
            super.insertString(offs, str, a);
        }
    }
}

private class HalfCostActionListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        mDeductFixedAmountTextField.setEditable(false);
        mDeductOtherFractionTextField.setEditable(false);
        mDeductFixedAmountTextField.setFocusable(false);
        mDeductOtherFractionTextField.setFocusable(false);
        updateBalanceDisplay(mDeductHalfTextField.getText());
    }
}

private class FourthCostActionListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        mDeductFixedAmountTextField.setEditable(false);
        mDeductOtherFractionTextField.setEditable(false);
        mDeductFixedAmountTextField.setFocusable(false);
        mDeductOtherFractionTextField.setFocusable(false);
        updateBalanceDisplay(mDeductFourthTextField.getText());
    }
}

private class FractionCostActionListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        mDeductOtherFractionTextField.setEditable(true);
        mDeductOtherFractionTextField.setFocusable(true);
        mDeductFixedAmountTextField.setEditable(false);
        mDeductFixedAmountTextField.setFocusable(false);
        updateBalanceDisplay(calculateFraction());
    }
}

private class FixedCostActionListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        mDeductFixedAmountTextField.setEditable(true);
        mDeductFixedAmountTextField.setFocusable(true);
        mDeductOtherFractionTextField.setEditable(false);
        mDeductOtherFractionTextField.setFocusable(false);
        updateBalanceDisplay(mDeductFixedAmountTextField.getText());
    }
}

private class FractionCostDocumentListener implements DocumentListener {
    public void insertUpdate(DocumentEvent e) {
        updateBalanceDisplay(calculateFraction());
    }

    public void removeUpdate(DocumentEvent e) {
        updateBalanceDisplay(calculateFraction());
    }

    public void changedUpdate(DocumentEvent e) {
        updateBalanceDisplay(calculateFraction());
    }
}

private class FixedCostDocumentListener implements DocumentListener {
    public void insertUpdate(DocumentEvent e) {
        updateBalanceDisplay(mDeductFixedAmountTextField.getText());
    }

    public void removeUpdate(DocumentEvent e) {
        updateBalanceDisplay(mDeductFixedAmountTextField.getText());
    }

    public void changedUpdate(DocumentEvent e) {
        updateBalanceDisplay(mDeductFixedAmountTextField.getText());
    }
}

private class WinnerBoxActionListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();
        if (source instanceof JComboBox) {
            boolean hasChoice = ((JComboBox) source).getSelectedIndex() != -1;
            mDialog.CommitAction.setEnabled(hasChoice);
        }
    }
}

private static final String NAME = "Resolve Winner";
private static final String SHORT_TEXT =
        "Execute a karma contest among the selected entries";
private static final String PICKUP_DIALOG_TITLE = "Pick-ups Detected!";
private static final String SUCCESS_STATUS = "Karma contest completed.";
private static final String CANCELED_STATUS = "Karma contest canceled.";

private final JTable mTable;
private final RosterTableModel<?> mModel;
private final SamsaraFrame mApp;
private ListingDialog<Raider> mDialog;
private final Map<ButtonModel, JComponent> mCostOptionsToValuesMap =
        new HashMap<ButtonModel, JComponent>();
private ButtonGroup mDeductButtonGroup;
private JComboBox mWinnerBox;
private JTextField mCommentTextField;
private JTextField mBalanceTextField;
private JTextField mKarmaTextField;
private JTextField mDeductHalfTextField;
private JTextField mDeductFourthTextField;
private JRadioButton mDeductHalfButton;
private JRadioButton mDeductFourthButton;
private JTextField mDeductOtherFractionTextField;
private JTextField mDeductFixedAmountTextField;
private final ActionListener mWinnerBoxActionListener;
}
